/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 */

import fs from 'fs';
import os from 'os';
import path from 'path';

import { hvigorCore, TaskDetails } from '@ohos/hvigor';
import * as fse from 'fs-extra';

import { parsingProfileName } from '../../common/common-util.js';
import { ohosTrace } from '../../common/trace/ohos-trace.js';
import { BuildDirConst } from '../../const/build-directory-const.js';
import { ArkTSVersionConst, CommonConst } from '../../const/common-const.js';
import { ApiVersion } from '../../const/sdk-const.js';
import { AotCompileModeEnum } from '../../enum/aot-compile-mode-enum.js';
import { ModuleModel } from '../../model/module/module-model.js';
import { ModuleTargetRes } from '../../model/res/res-model.js';
import { LegacyTargetSourceSetModel, TargetSourceSetModel } from '../../model/source-set/source-set-model.js';
import { BuildOpt, NativeLib } from '../../options/build/build-opt.js';
import { ModuleJson } from '../../options/configure/module-json-options.js';
import { RouterMapOptions } from '../../options/configure/router-map-options.js';
import { buildOptionManager } from '../../project/build-option/build-mode-manager.js';
import { SdkInfo } from '../../sdk/sdk-info.js';
import { SdkLoader } from '../../sdk/sdk-loader.js';
import { CmakeUtil } from '../../utils/cmake-util.js';
import { FileUtil } from '../../utils/file-util.js';
import { getJson5Obj } from '../../utils/json-util.js';
import { resModelLoader } from '../../utils/loader/file/res-model-loader.js';
import { OhosLogger } from '../../utils/log/ohos-logger.js';
import { ValidateUtil } from '../../utils/validate/validate-util.js';
import { ModuleTargetData } from '../data/hap-task-target-data.js';
import { ModuleTaskService } from './module-task-service.js';

import ModuleOptObj = ModuleJson.ModuleOptObj;
import RouterMapObj = RouterMapOptions.RouterMapObj;

import { isAtomicServiceProject } from '../../utils/task-util.js';
import { shouldTreatHarAsHap } from '../../utils/byte-code-har-utils.js';
import { Dependency } from '../../project/dependency/core/dependency-interface.js';

const _log = OhosLogger.getLogger('Hvigor-target-service');

/**
 * 以Target为维度创建不同的task，提供target相关的数据信息和管理
 *
 * @since 2022/8/11
 */
export class TargetTaskService {
  private targetData: ModuleTargetData;
  private buildOption: BuildOpt;
  private readonly _sdkInfo: SdkInfo;
  private readonly moduleService: ModuleTaskService;
  private readonly shouldBuildCmake: boolean;

  constructor(targetData: ModuleTargetData, moduleService: ModuleTaskService) {
    this.targetData = targetData;
    this.buildOption = buildOptionManager.getTargetBuildOption(targetData.getModuleModel().getName(), targetData.getTargetName())!;
    // autoLazyImport打点
    const isAutoLazyImport = this.buildOption?.arkOptions?.autoLazyImport;
    ohosTrace.traceIsAutoLazyImport(targetData.getModuleModel().getName(), isAutoLazyImport);
    this.moduleService = moduleService;
    const product = targetData.getProduct();
    const profilePath = this.moduleService.getProjectModel().getProfilePath();
    const isAtomicService = isAtomicServiceProject(product, moduleService.getProjectModel());
    ValidateUtil.integrationVersionCheck(targetData.isHarmonyOS(), product, isAtomicService, profilePath);
    this._sdkInfo = SdkLoader.getSdkInfo(this.targetData.getApiMeta().compileSdkVersion, !this.targetData.isHarmonyOS());
    const nativeOption = this.getBuildOption().externalNativeOptions;

    // 判断当前工程是否需要进行so编译
    this.shouldBuildCmake = CmakeUtil.nativeTaskCondition(this.targetData.getModuleModel(), this.targetData, nativeOption);
  }

  refreshBuildOption() {
    this.buildOption = buildOptionManager.getTargetBuildOption(this.targetData.getModuleModel().getName(), this.targetData.getTargetName())!;
    // autoLazyImport打点
    const isAutoLazyImport = this.buildOption?.arkOptions?.autoLazyImport;
    ohosTrace.traceIsAutoLazyImport(this.targetData.getModuleModel().getName(), isAutoLazyImport);
  }

  public buildTaskName(taskDetails: TaskDetails, targetName?: string): TaskDetails {
    return {
      ...taskDetails,
      name: `${computeTargetTaskName(targetName ?? this.getTargetData().getTargetName(), taskDetails.name!)}`,
    };
  }

  public getTargetData(): ModuleTargetData {
    return this.targetData;
  }

  public getBuildOption(): BuildOpt {
    return this.buildOption;
  }

  public isByteCodeHar(): boolean {
    if (!this.moduleService.getModuleModel().isHarModule() || shouldTreatHarAsHap(this.targetData.getTargetName())) {
      return false;
    }

    const useNormalizedOHMUrl = this.getBuildOption()?.strictMode?.useNormalizedOHMUrl;
    const byteCodeHar = this.getBuildOption().arkOptions?.byteCodeHar;
    return this.getByteCodeHar(useNormalizedOHMUrl, byteCodeHar);
  }

  /**
   * 获取byteCodeHar真实值
   * useNormalizedOHMUrl=true
   *    byteCodeHar若无配置则默认为true
   * useNormalizedOHMUrl=false
   *    byteCodeHar若无配置则默认为false
   * 非上述默认情况则以实际配置值为准
   *
   * @param useNormalizedOHMUrl 使用新格式ohmurl 默认为false 需显示配置才可生效
   * @param byteCodeHar
   */
  public getByteCodeHar(useNormalizedOHMUrl: boolean | undefined, byteCodeHar: boolean | undefined): boolean {
    if (useNormalizedOHMUrl && byteCodeHar === undefined) {
      return true;
    }

    if (!useNormalizedOHMUrl && !byteCodeHar) {
      return false;
    }

    return !!byteCodeHar;
  }

  /**
   * 获取noExternalImportByPath真实值
   * useNormalizedOHMUrl=true时
   *    noExternalImportByPath若无配置则默认为true
   * 非上述默认情况则以实际配置值为准
   */
  public isNoExternalImportByPath(): boolean {
    const useNormalizedOHMUrl = this.getBuildOption()?.strictMode?.useNormalizedOHMUrl;
    const noExternalImportByPath = this.buildOption?.strictMode?.noExternalImportByPath;
    if (useNormalizedOHMUrl && noExternalImportByPath === undefined) {
      return true;
    }

    return !!noExternalImportByPath;
  }

  /**
   * 判断是否为源码har
   */
  public isSourceCodeHar(): boolean {
    return this.moduleService.getModuleModel().isHarModule() && !this.isByteCodeHar();
  }

  public isBundledDependencies(): boolean {
    return !!this.getBuildOption()?.arkOptions?.bundledDependencies;
  }

  public getNativeLibOption(): NativeLib {
    return {
      ...this.getBuildOption().nativeLib,
      filter: { ...this.getBuildOption().napiLibFilterOption, ...this.getBuildOption().nativeLib?.filter },
    };
  }

  /**
   * 提供当前target绝对路径化的workerConfig
   * @returns {string[]}
   */
  public getWorkerConfig(): string[] {
    return FileUtil.convertToAbsolutePaths(this.buildOption?.sourceOption?.workers ?? [], this.moduleService.getModuleModel().getProjectDir());
  }

  /**
   * 获取模块级build-profile.json5中所有自定义buildProfileFields的变量
   */
  public getBuildProfileFields(): object | undefined {
    return this.getBuildOption()?.arkOptions?.buildProfileFields;
  }

  /**
   * 获取模块target资源目录集合
   */
  getResourceDirs(): string[] {
    const customDirectories: string[] | undefined = this.targetData.getTargetOpt().resource?.directories;

    // 检查是否配置了自定义资源目录, 如果未配置, 则使用默认资源目录src/main/resources
    if (!customDirectories?.length) {
      const defaultResourceDir: string = path.resolve(this.targetData.getModuleSourceSetModel().getSourceSetRoot(), BuildDirConst.RESTOOL_BUILD_RESOURCES);
      return fs.existsSync(defaultResourceDir) ? this.convertResourceAbsolutePath([defaultResourceDir]) : [];
    }
    return this.convertResourceAbsolutePath(customDirectories);
  }

  /**
   *  相对路径转换为资源绝对路径并校验
   *
   * @param {string}  resourcesDirs 资源路径
   */
  private convertResourceAbsolutePath(resourcesDirs: string[]) {
    return resourcesDirs.map((dir) => {
      const absoluteDir = path.isAbsolute(dir) ? dir : path.resolve(this.moduleService.getModuleModel().getProjectDir(), dir);
      ValidateUtil.validateTargetCustomizeResources(dir, absoluteDir);
      return absoluteDir;
    });
  }

  getAnBuildMode(): AotCompileModeEnum {
    const apiVersion = this.targetData.getCompileApiVersion();
    const aotCompileMode = this.getBuildOption()?.arkOptions?.aotCompileMode ?? this.getBuildOption()?.aotCompileMode;
    const hostPGO = this.getBuildOption().arkOptions?.hostPGO;
    switch (apiVersion) {
      case ApiVersion.API_VERSION_9:
        return aotCompileMode ?? AotCompileModeEnum.AOT_NULL;
      case ApiVersion.API_VERSION_10:
      default:
        // hos sdk断代，只检查ohos的sdk版本
        if (this._sdkInfo.isOhos && !this._sdkInfo.checkAotFixedSdkVersion() && !hostPGO) {
          return AotCompileModeEnum.AOT_NULL;
        }
        return hostPGO ? AotCompileModeEnum.AOT_PARTIAL : AotCompileModeEnum.AOT_TYPE;
    }
  }

  // api10起该值有缺省值./modules.ap
  getApPath(): string | undefined {
    return (
      this.getBuildOption()?.arkOptions?.apPath ??
      this.getBuildOption()?.apPath ??
      (this.targetData.getCompileApiVersion() >= ApiVersion.API_VERSION_10 ? './modules.ap' : undefined)
    );
  }

  getApAbsolutePath(): string {
    const aotApPath = this.getApPath();
    const modulePath = this.targetData.getModuleModel().getProjectDir();
    return aotApPath ? path.resolve(modulePath, aotApPath) : modulePath;
  }

  getCustomTypes(): string[] | undefined {
    return this.getBuildOption()?.arkOptions?.types;
  }

  public isDebug() {
    const debuggable = this.buildOption.debuggable;
    return debuggable === undefined || debuggable;
  }

  /**
   * 判断是否为开源/闭源
   * 1.混淆配置仅release生效
   * 2.debug模式下均为开源
   * 3.release模式下检查arkOptions?.obfuscation?.ruleOptions?.enable字段
   *    3.1.若enable配置为false或缺省,则不开启混淆为开源
   *    3.2.若enable配置为true,则开启混淆为闭源
   */
  public isOpenSource() {
    if (!this.isDebug()) {
      const enableObfuscation: boolean | undefined = this.getBuildOption()?.arkOptions?.obfuscation?.ruleOptions?.enable;
      if (enableObfuscation === undefined || !enableObfuscation) {
        // enable为false时或缺省时默认不混淆为开源
        return true;
      }
      if (enableObfuscation) {
        // enable为true时为闭源
        return false;
      }
    }
    return true;
  }

  public getShouldBuildCmake(): boolean {
    return this.shouldBuildCmake;
  }

  /**
   * 判断构建模式是debug还是release
   */
  public getBuildMode() {
    return this.isDebug() ? 'Debug' : 'Release';
  }

  public needPackBin(target: ModuleTargetData): boolean {
    return target.hasLiteDeviceInTarget() && this.isDebug() && !hvigorCore.isCommandEntryTask(CommonConst.ASSEMBLE_APP);
  }

  public getSdkInfo(): SdkInfo {
    return this._sdkInfo!;
  }

  public getModuleService() {
    return this.moduleService;
  }

  public static getFormJsonArr(targetRes: ModuleTargetRes): string[] {
    const targetModuleOptObj = targetRes.getModuleJsonOpt();
    const targetResourcesPath = targetRes.getResourcePath();
    const targetJsonPath = targetRes.getJsonPath();
    const formJsonArr: string[] = [];
    if (!targetModuleOptObj?.module.extensionAbilities) {
      return formJsonArr;
    }
    targetModuleOptObj.module.extensionAbilities?.forEach((extAbility) => {
      if (extAbility.type === 'form') {
        if (!extAbility.metadata?.length) {
          _log.printErrorExit('THE_METADATE_FIELD_IN_FORMEXTENSIONABILITY_CANNOT_BE_LEFT_BLANK_OR_AS_AN_EMPTY_ARRAY',
            [targetJsonPath],[[targetJsonPath]]);
          return;
        }
        const extensionForm = extAbility.metadata.find((v) => v.name === 'ohos.extension.form');
        if (!extensionForm?.resource) {
          _log.printErrorExit('IN_FORMEXTENSIONABILITY_METADATA_MUST_CONTAIN_AN_OBJECT_WITH_ITS_NAME',
            [targetJsonPath], [[targetJsonPath]]);
          return;
        }
        for (const metadata of extAbility.metadata) {
          if (metadata.resource) {
            if (typeof metadata.resource !== 'string') {
              _log.printErrorExit('METADATA_RESOURCE_NOT_STRING', [targetJsonPath]);
            }
            const formJsonFileName = `${metadata.resource.replace(/\$profile:/, '')}.json`;
            const formJsonPath = path.resolve(targetResourcesPath, BuildDirConst.BASE, BuildDirConst.PROFILE, formJsonFileName);
            this.formJsonFileCheck(formJsonPath, metadata, targetJsonPath);
            formJsonArr.push(formJsonPath);
          }
        }
      }
    });
    return formJsonArr;
  }

  private static formJsonFileCheck(formJsonPath: string, metadata: ModuleJson.MetadataObj, moduleJson5Path: string): void {
    if (!fs.existsSync(formJsonPath)) {
      const lines = fs.readFileSync(moduleJson5Path).toString().split('\n');
      let lineNum = -1;
      let columnNum = -1;
      lines.some((value: string, index: number) => {
        if ((columnNum = value.indexOf(metadata.resource as string)) !== -1) {
          lineNum = index;
          return true;
        }
        return false;
      });
      _log.printErrorExit('CAN_NOT_RESOLVE_SYMBOL', [metadata.resource, moduleJson5Path, lineNum + 1, columnNum + 1 + (metadata.resource as string).length]);
    }
  }

  /**
   * 获取startup文件名
   */
  public getAppStartupFileName(isFaMode: boolean){
    let jsonPath;
    if(isFaMode) {
      const targetSourceSetModel = this.targetData.getModuleSourceSetModel() as LegacyTargetSourceSetModel;
      jsonPath = targetSourceSetModel.getLegacyModuleTargetRes().getJsonPath();
    } else {
      const targetSourceSetModel = this.targetData.getModuleSourceSetModel() as TargetSourceSetModel;
      jsonPath = targetSourceSetModel.getModuleTargetRes().getJsonPath();
    }
    const moduleOptObj: ModuleOptObj = resModelLoader.getModuleJson(jsonPath);
    const appStartup = moduleOptObj.module.appStartup;
    return parsingProfileName(appStartup);
  }
  /**
   * 获取appStartup配置文件路径
   * @private
   */
  public getAppStartupPath(isFaMode: boolean ): string | undefined {
    const appStartupProfileName = this.getAppStartupFileName(isFaMode);
    if (!appStartupProfileName) {
      return undefined;
    }
    return this.getTargetResourceBaseProfilePath(`${appStartupProfileName}.json`);
  }

  /**
   * 获取指定根模块的shortcutsJson配置路径
   * @private
   * @param moduleModel
   */
  public getShortcutsJsonPaths(moduleModel: ModuleModel | undefined): string[] {

    const shortcutsFileNames = this.getShortcutsJsonFileNames(moduleModel);
    if (!shortcutsFileNames) {
      return [];
    }

    const ShortcutsJsonPaths:string[] = [];
    // 解析相对路径后，如果存在路径则将绝对路径存入数组，否则保留相对路径存入数组，用来作为报错信息
    shortcutsFileNames.forEach(shortcutsFileName => {
      ShortcutsJsonPaths.push(this.getTargetResourceBaseProfilePath(`${shortcutsFileName}.json`) ?? `${shortcutsFileName}.json`);
    });
    return ShortcutsJsonPaths;
  }

  /**
   * 获取shortcuts配置文件名
   * @param moduleModel
   * @private
   */
  public getShortcutsJsonFileNames(moduleModel: ModuleModel | undefined): string[] {
    if (!moduleModel) {
      return [];
    }

    const moduleJsonPath = path.resolve(moduleModel.getModule().getNodeDir(), BuildDirConst.SRC, BuildDirConst.MAIN, CommonConst.MODULE_JSON5);
    const moduleJsonObj: ModuleOptObj = resModelLoader.getModuleJson(moduleJsonPath);
    const shortcutsList: string[] = [];
    const shortcutsProfileNames: string[] = [];
    // 拿出abilities里面的metadata
    if (moduleJsonObj?.module?.abilities){
      moduleJsonObj.module.abilities.forEach(ability => {
        if (ability?.metadata){
          ability.metadata.forEach(metadataElement => {
            if (metadataElement?.name === CommonConst.SHORTCUTS_FIELD_NAME && metadataElement.resource){
              shortcutsList.push(metadataElement.resource);
            }
          });
        }
      });
    }
    // 拿出extensionAbilities里面的metadata
    if (moduleJsonObj?.module?.extensionAbilities){
      moduleJsonObj.module.extensionAbilities.forEach(ability => {
        if (ability?.metadata){
          ability.metadata.forEach(metadataElement => {
            if (metadataElement?.name === CommonConst.SHORTCUTS_FIELD_NAME && metadataElement.resource){
              shortcutsList.push(metadataElement.resource);
            }
          });
        }
      });
    }
    // 解析$profile的路径
    shortcutsList.forEach(shortcut => {
      if (parsingProfileName(shortcut)){
        shortcutsProfileNames.push(parsingProfileName(shortcut)!);
      }
    });
    return shortcutsProfileNames;
  }

  /**
   * 获取指定根模块的routerMap配置路径
   * @private
   * @param moduleModel
   */
  public getRouterMapJsonPath(moduleModel: ModuleModel | undefined): string | undefined {
    const routerMapFileName = this.getRouterMapJsonFileName(moduleModel);
    if (!routerMapFileName) {
      return undefined;
    }
    return this.getTargetResourceBaseProfilePath(`${routerMapFileName}.json`);
  }

  /**
   * 获取模块路由表配置文件名
   * @param moduleModel
   * @private
   */
  public getRouterMapJsonFileName(moduleModel: ModuleModel | undefined): string | undefined {
    if (!moduleModel) {
      return undefined;
    }

    const moduleJsonPath = path.resolve(moduleModel.getModule().getNodeDir(), BuildDirConst.SRC, BuildDirConst.MAIN, CommonConst.MODULE_JSON5);
    const moduleJsonObj: ModuleOptObj = resModelLoader.getModuleJson(moduleJsonPath);
    const routerMap = moduleJsonObj.module.routerMap;
    return parsingProfileName(routerMap);
  }

  /**
   * 根据target定制资源目录获取对应routerMapObjList
   * @param moduleModel
   * @private
   */
  public getTargetRouterMapObjList(moduleModel: ModuleModel): RouterMapObj[] | undefined {
    const targetRouterMapJsonPath = this.getRouterMapJsonPath(moduleModel);
    if (!targetRouterMapJsonPath || !fse.existsSync(targetRouterMapJsonPath)) {
      return undefined;
    }

    const targetRouterMapOpt: RouterMapOptions.RouterMapOpt = getJson5Obj(targetRouterMapJsonPath);
    const targetRouterMapObjList = targetRouterMapOpt.routerMap;
    targetRouterMapObjList.forEach((localRouterMapObj) => {
      Object.assign(localRouterMapObj, { moduleNodeDir: moduleModel.getModule().getNodeDir() });
    });
    return targetRouterMapOpt?.routerMap;
  }

  /**
   * 获取target定制资源base/profile下的目录地址
   */
  public getTargetResourceBaseProfilePath(relativePath: string): string | undefined {
    for (const resourceDir of this.getResourceDirs()) {
      const filePath = path.resolve(resourceDir, CommonConst.BASE_PROFILE, relativePath);
      if (fse.existsSync(filePath)) {
        return filePath;
      }
    }
    return undefined;
  }

  /**
   * 获取模块对应pkgContextInfo
   * @param packageName 包名
   */
  public getPkgContextInfo(packageName: string): any {
    const pkgContextInfoPath = this.targetData.getPathInfo().getIntermediatesPkgContextInfoPath();
    const pkgContextInfos = getJson5Obj(pkgContextInfoPath);
    if(!pkgContextInfos) {
      return {};
    }
    return pkgContextInfos[packageName];
  }

  /**
   * 获取原始srcEntry的绝对路径
   * srcEntry在编辑器中限制只能以./开头,即相对于module.json
   * @param originalSrcEntry
   * @param rootPath
   */
  public getAbsoluteSrcEntryPath(originalSrcEntry: string, rootPath: string): string {
    return path.resolve(rootPath, `${BuildDirConst.SRC}${path.sep}${BuildDirConst.MAIN}`, originalSrcEntry);
  }

  public getSelfArkTSVersion(): string {
    const projectArkTSVersion = this.targetData.getProduct().arkTSVersion;
    const moduleArkTSVersion = this.moduleService.getModuleModel().getProfileOpt().arkTSVersion;
    return this.moduleService.arkTSVersionArbitration(moduleArkTSVersion, projectArkTSVersion);
  }

  public verifyModuleVersionIsArkTS12(dependency?: Dependency): boolean {
    if (dependency && dependency.getModuleName()) {
      return this.getSelfArkTSVersionByModuleName(dependency.getModuleName()) === ArkTSVersionConst.ARKTS1_2;
    }
    return this.getSelfArkTSVersion() === ArkTSVersionConst.ARKTS1_2;
  }

  getSelfArkTSVersionByModuleName(moduleName: string): string {
    for (const dependency of this.moduleService.getAllDependencies()) {
      if (dependency.getModuleName() === moduleName) {
        return this.moduleService.getDependencySelfArkTSVersion(dependency);
      }
    }
    return ArkTSVersionConst.DEFAULT;
  }
}

/**
 * 拼接不同target的任务名
 *
 * @param {string} targetName
 * @param {string} taskName
 * @return {string}
 */
export function computeTargetTaskName(targetName: string, taskName: string): string {
  return `${targetName}@${taskName}`;
}
